﻿
using ED.Common.BaseModel;
using ED.Common.Extensions;
using FreeSql;
using FreeSql.Aop;
using FreeSql.Internal.ObjectPool;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace ED.FreeSql.Core
{
    /// <summary>
    /// 异步上下文对象
    /// </summary>
    public class AsyncOrmContext
    {
        /// <summary>
        /// 已经开启的次数
        /// </summary>
        public int TranCounter { get; set; }
        /// <summary>
        /// 连接对象
        /// </summary>
        public Object<DbConnection> Conn { get; set; }
        /// <summary>
        /// 事务对象
        /// </summary>
        public DbTransaction Tran { get; set; }
    }

    public static class FreeExtensions
    {
        #region 全局单例
        public static IUser User { private set; get; }
        public static IFreeSql FreeSql { private set; get; }
        public static IHeaderExt HeaderExt { private set; get; }
        public static IHttpContextAccessor ContextAccessor { private set; get; }

        //异步上下文: 异步事务
        private static AsyncLocal<AsyncOrmContext> _asyncContext = new AsyncLocal<AsyncOrmContext>();
        static FreeExtensions()
        {
            _asyncContext.Value = new AsyncOrmContext();
        }
        public static AsyncOrmContext AsyncContext
        {
            get
            {
                if (_asyncContext == null)
                {
                    _asyncContext = new AsyncLocal<AsyncOrmContext>();
                }
                if (_asyncContext.Value == null)
                {
                    _asyncContext.Value = new AsyncOrmContext();
                }

                return _asyncContext.Value;
            }
        }

        /// <summary>
        /// 为FreeSql准备对象资源（主要是从容器中获取IHttpContextAccessor）
        /// </summary>
        /// <param name="applicationBuilder"></param>
        public static void UseFreeSql(this IApplicationBuilder applicationBuilder)
        {
            User = applicationBuilder.ApplicationServices.GetRequiredService<IUser>();
            FreeSql = applicationBuilder.ApplicationServices.GetRequiredService<IFreeSql>();
            HeaderExt = applicationBuilder.ApplicationServices.GetRequiredService<IHeaderExt>();
            ContextAccessor =applicationBuilder.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
        }
        #endregion

        #region 用于注入服务的扩展
        public static IServiceCollection AddFreeSql(this IServiceCollection services, DataType dataType, string connectString)
        {
            var fsql = new FreeSqlBuilder().UseConnectionString(dataType, connectString).Build();
            return AddFreeSqlInternal(services, fsql);
        }

        private static IServiceCollection AddFreeSqlInternal(this IServiceCollection services, IFreeSql fsql)
        {
            //注入全局过滤: 会对所有的审计实体的,select、update、delete起作用
            fsql.GlobalFilter.Apply<IAuditDeleted>("SoftDelete", a => a.IsDeleted == false);
            //注入插入/更新的aop拦截
            //AddAopInterceptorForInsertAndUpdate(fsql);
            services.AddSingleton<IFreeSql>(fsql);
            AddFreeSqlRepository(services);
            return services;
        }

        public static IServiceCollection AddFreeSql(this IServiceCollection services, Action<FreeSqlBuilder> builderConfig)
        {
            var builder = new FreeSqlBuilder();
            builderConfig?.Invoke(builder);
            var fsql = builder.Build();
            return AddFreeSqlInternal(services, fsql);
        }

        private static void AddFreeSqlRepository(IServiceCollection services)
        {
            services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
            services.AddScoped(typeof(IRepository<,>), typeof(Repository<,>));
        }

        private static void AddAopInterceptorForInsertAndUpdate(IFreeSql freeSql)
        {
            var now = DateTime.Now;
            freeSql.Aop.AuditValue += (s, e) =>
            {
                switch (e.AuditValueType)
                {
                    case AuditValueType.Update:
                        if (e.Property.Name == nameof(IAuditUpdated.UpdatedDate))
                        {
                            if (e.Value.IsDefault()) e.Value = now;
                        }
                        if (e.Property.Name == nameof(IAuditUpdated.UpdatedBy))
                        {
                            e.Value = User.Id;//UserId
                        }
                        break;
                    case AuditValueType.Insert:
                        if (e.Property.Name == nameof(IAuditCreated.CreatedDate))
                        {
                            e.Value = now;
                        }
                        if (e.Property.Name == nameof(IAuditCreated.CreatedBy))
                        {
                            e.Value = User.Id;//UserId
                        }
                        if (e.Property.Name == nameof(IAuditDeleted.IsDeleted))
                        {
                            e.Value = 0;//IsDeleted
                        }
                        if (e.Property.Name == nameof(IAuditUpdated.UpdatedBy))
                        {
                            e.Value = User.Id;//UpdatedBy
                        }
                        if (e.Property.Name == nameof(IAuditUpdated.UpdatedDate))
                        {
                            e.Value = now;//UpdatedDate
                        }
                        break;
                    case AuditValueType.InsertOrUpdate:
                        break;
                    default:
                        break;
                }
            };
        }
        #endregion

        #region 禁用审计操作(不需要)
        //public static ISelect<TEntity> UseHardOparate<TEntity>(this ISelect<TEntity> select) where TEntity : class
        //{
        //    return select.DisableGlobalFilter("SoftDelete");
        //}

        //public static IUpdate<TEntity> UseHardOparate<TEntity>(this IUpdate<TEntity> select) where TEntity : class
        //{
        //    return select.DisableGlobalFilter("SoftDelete");
        //}

        //public static IDelete<TEntity> UseHardOparate<TEntity>(this IDelete<TEntity> select) where TEntity : class
        //{
        //    return select.DisableGlobalFilter("SoftDelete");
        //}
        #endregion

        #region 分页扩展
        public static PageOut<TOut> GetPage<TEntity, TOut>(this ISelect<TEntity> query, int pageIndex, int pageSize) where TEntity : class
        {
            long total;
            var list = query.Count(out total).Page(pageIndex, pageSize).ToList<TOut>();
            return new PageOut<TOut> { List = list, TotalCount = total };
        }
        public static Task<PageOut<TOut>> GetPageAsync<TEntity, TOut>(this ISelect<TEntity> query, int pageIndex, int pageSize, CancellationToken cancellationToken = default) where TEntity : class
        {
            long total;
            return query.Count(out total).Page(pageIndex, pageSize).ToListAsync<TOut>(cancellationToken)
                .ContinueWith(pre => new PageOut<TOut>
                {
                    List = pre.Result,
                    TotalCount = total
                });
        }
        public static PageOut<TEntity> GetPage<TEntity>(this ISelect<TEntity> query, int pageIndex, int pageSize) where TEntity : class
        {
            long total;
            var list = query.Count(out total).Page(pageIndex, pageSize).ToList();
            return new PageOut<TEntity> { List = list, TotalCount = total };
        }

        public static Task<PageOut<TEntity>> GetPageAsync<TEntity>(this ISelect<TEntity> query, int pageIndex, int pageSize, bool includeNestedMembers, CancellationToken cancellationToken = default) where TEntity : class
        {
            long total;
            return query.Count(out total).Page(pageIndex, pageSize).ToListAsync(includeNestedMembers, cancellationToken)
                .ContinueWith(pre => new PageOut<TEntity> { List = pre.Result, TotalCount = total });
        }

        public static Task<PageOut<TEntity>> GetPageAsync<TEntity>(this ISelect<TEntity> query, int pageIndex, int pageSize, CancellationToken cancellationToken = default) where TEntity : class
        {
            return GetPageAsync(query, pageIndex, pageSize, false, cancellationToken);
        }

        public static ISelect<TEntity> ToPage<TEntity>(this ISelect<TEntity> query, int pageIndex, int pageSize, out long total) where TEntity : class
        {
            var list = query.Count(out total).Page(pageIndex, pageSize);
            return list;
        }

        public static PageOut<TEntity> ToPage<TEntity>(this ISelect<TEntity> query, int pageIndex, int pageSize) where TEntity : class
        {
            var list = query.Count(out long total).Page(pageIndex, pageSize);
            return new PageOut<TEntity>()
            {
                List = list.ToList(),
                TotalCount = total
            };
        }
        #endregion

        #region 异步事务扩展
        /// <summary>
        /// 开启异步事务(有返回值),由ED.FreeSql.Core中扩展
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="fsql"></param>
        /// <param name="hander"></param>
        /// <param name="isolationLevel"></param>
        /// <remarks>注意：尽量不要在事务中写查询代码，必须写的情况下需要手动给ISelect对象绑定异步事务对象(FreeExtensions.AsyncContext?.Tran) (Repository/Service对象的查询方法不需要)，否则容易引起死锁</remarks>
        /// <returns></returns>
        public static async Task<T> TransactionAsync<T>(this IFreeSql fsql, Func<Task<T>> hander, IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
        {
            #region 开启事务
            var current = AsyncContext.Tran;
            if (current != null)
            {
                //已经开启了事务
                AsyncContext.TranCounter++;
            }
            else
            {
                //首次开启事务
                AsyncContext.Conn = fsql.Ado.MasterPool.Get();
                try
                {
                    AsyncContext.Tran = AsyncContext.Conn.Value.BeginTransaction(isolationLevel);
                    AsyncContext.TranCounter = 1;
                }
                catch (Exception ex)
                {
                    AsyncContext.Conn.ResetValue();
                    fsql.Ado.MasterPool.Return(AsyncContext.Conn);
                    AsyncContext.Tran = null;
                    AsyncContext.TranCounter = 0;
                    throw ex;
                }
            }
            #endregion
            T res = default(T);
            try
            {
                res = await hander?.Invoke();
                AsyncContext.TranCounter--;
                if (AsyncContext.TranCounter == 0)
                {
                    //提交事务
                    AsyncContext.Tran.Commit();
                    AsyncContext.Conn.ResetValue();
                    fsql.Ado.MasterPool.Return(AsyncContext.Conn);

                    AsyncContext.Tran = null;
                    AsyncContext.Conn = null;
                }
                else
                {
                    //等待外层最终提交
                }
            }
            catch (Exception ex)
            {
                try
                {
                    AsyncContext.Tran?.Rollback();
                }
                catch { }
                AsyncContext.Conn?.ResetValue();
                fsql.Ado.MasterPool.Return(AsyncContext.Conn);
                AsyncContext.Tran = null;
                AsyncContext.Conn = null;
                throw ex;
            }
            return res;
        }

        public static T TransactionSync<T>(this IFreeSql fsql, Func<T> hander, IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
        {
            T res = default(T);
            fsql.Transaction(isolationLevel: isolationLevel, () =>
            {
                AsyncContext.Tran = fsql.Ado.TransactionCurrentThread; 
                if (hander != null)
                {
                    res = hander.Invoke();
                }
            });
            return res;
        }
        public static void TransactionSync(this IFreeSql fsql, Action hander, IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
        { 
            fsql.Transaction(isolationLevel: isolationLevel, () =>
            {
                AsyncContext.Tran = fsql.Ado.TransactionCurrentThread; 
                if (hander != null)
                {
                   hander.Invoke();
                }
            });
          
        }
        /// <summary>
        /// 开启异步事务(无返回值),由ED.FreeSql.Core中扩展
        /// </summary>
        /// <param name="fsql"></param>
        /// <param name="hander"></param>
        /// <param name="isolationLevel"></param>
        /// <returns></returns>
        public static async Task TransactionAsync(this IFreeSql fsql, Func<Task> hander, IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
        {
            #region 开启事务
            var current = AsyncContext.Tran;
            if (current != null)
            {
                //已经开启了事务
                AsyncContext.TranCounter++;
            }
            else
            {
                //首次开启事务
                AsyncContext.Conn = fsql.Ado.MasterPool.Get();
                try
                {
                    AsyncContext.Tran = AsyncContext.Conn.Value.BeginTransaction(isolationLevel);
                    AsyncContext.TranCounter = 1;
                }
                catch (Exception ex)
                {
                    AsyncContext.Conn.ResetValue();
                    fsql.Ado.MasterPool.Return(AsyncContext.Conn);
                    AsyncContext.Tran = null;
                    AsyncContext.TranCounter = 0;
                    throw ex;
                }
            }
            #endregion
            try
            {
                await hander?.Invoke();
                AsyncContext.TranCounter--;
                if (AsyncContext.TranCounter == 0)
                {
                    //提交事务
                    AsyncContext.Tran.Commit();
                    AsyncContext.Conn.ResetValue();
                    fsql.Ado.MasterPool.Return(AsyncContext.Conn);

                    AsyncContext.Tran = null;
                    AsyncContext.Conn = null;
                }
                else
                {
                    //等待外层最终提交
                }
            }
            catch (Exception ex)
            {
                try
                {
                    AsyncContext.Tran?.Rollback();
                }
                catch
                {
                }
                AsyncContext.Conn?.ResetValue();
                fsql.Ado.MasterPool.Return(AsyncContext.Conn);
                AsyncContext.Tran = null;
                AsyncContext.Conn = null;
                throw ex;
            }
        }
        #endregion

        #region 仓储扩展
        public static IRepository<TEntity> GetRepository<TEntity>(this IFreeSql freesql) where TEntity : BaseEntity<int>, new()
        {
            return new Repository<TEntity>();
        }

        public static IRepository<TEntity, TPrimaryKey> GetRepository<TEntity, TPrimaryKey>(this IFreeSql freesql) where TEntity : BaseEntity<TPrimaryKey>, new() where TPrimaryKey : struct
        {
            return new Repository<TEntity, TPrimaryKey>();
        }
        #endregion

        #region 设置插入和更新时审计属性值
        /// <summary>
        /// 设置插入和更新时审计属性值
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="update"></param>
        /// <param name="dto"></param>
        /// <param name="isDto">是否是dto</param>
        /// <returns></returns>
        internal static IUpdate<TEntity> SetAuditValue<TEntity>(this IUpdate<TEntity> update, object dto, bool isDto) where TEntity : class, IBaseEntity
        {
            var ignores = new List<string>();
            //更新时忽略主键
            if (typeof(IBaseEntity).IsAssignableFrom(typeof(TEntity)))
            {
                ignores.Add(nameof(BaseEntity.Id));
            }
            //更新时忽略插入属性
            if (typeof(IAuditCreated).IsAssignableFrom(typeof(TEntity)))
            {
                ignores.Add(nameof(IAuditCreated.CreatedBy));
                ignores.Add(nameof(IAuditCreated.CreatedDate));
            }
            //更新时忽略删除属性
            if (typeof(IAuditDeleted).IsAssignableFrom(typeof(TEntity)))
            {
                ignores.Add(nameof(IAuditDeleted.IsDeleted));
            }
            if (ignores.Count > 0) update.IgnoreColumns(ignores.ToArray());
            //更新时强制使用审计的更新值
            if (typeof(IAuditUpdated).IsAssignableFrom(typeof(TEntity)))
            {
                if (isDto)
                {
                    //dto是匿名对象时无法直接改值
                    var dic = dto.ToDictionary();
                    //当dto是自身时先移除掉
                    if (dic.ContainsKey(nameof(IAuditUpdated.UpdatedBy))) dic.Remove(nameof(IAuditUpdated.UpdatedBy));
                    if (dic.ContainsKey(nameof(IAuditUpdated.UpdatedDate))) dic.Remove(nameof(IAuditUpdated.UpdatedDate));
                    dic.Add(nameof(IAuditUpdated.UpdatedBy), User.Id);
                    dic.Add(nameof(IAuditUpdated.UpdatedDate), DateTime.Now);
                    update.SetDto(dic);
                }
                else
                {
                    //非dto情况下直接修改值
                    var ent = dto as IAuditUpdated;
                    ent.UpdatedBy = User.Id;
                    ent.UpdatedDate = DateTime.Now;
                    update.SetSource((TEntity)dto);
                }
            }
            else
            {
                if (isDto) update.SetDto(dto);
                else update.SetSource((TEntity)dto);
            }
            return update;
        }

        /// <summary>
        /// 设置插入和更新时审计属性值
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="update"></param>
        /// <param name="ents"></param>
        /// <returns></returns>
        internal static IUpdate<TEntity> SetAuditValue<TEntity>(this IUpdate<TEntity> update, List<TEntity> ents) where TEntity : class, IBaseEntity
        {
            var ignores = new List<string>();
            //更新时忽略主键
            if (typeof(IBaseEntity).IsAssignableFrom(typeof(TEntity)))
            {
                ignores.Add(nameof(BaseEntity.Id));
            }
            //更新时忽略插入属性
            if (typeof(IAuditCreated).IsAssignableFrom(typeof(TEntity)))
            {
                ignores.Add(nameof(IAuditCreated.CreatedBy));
                ignores.Add(nameof(IAuditCreated.CreatedDate));
            }
            //更新时忽略删除属性
            if (typeof(IAuditDeleted).IsAssignableFrom(typeof(TEntity)))
            {
                ignores.Add(nameof(IAuditDeleted.IsDeleted));
            }
            if (ignores.Count > 0) update.IgnoreColumns(ignores.ToArray());
            //更新时强制使用审计的更新值
            if (typeof(IAuditUpdated).IsAssignableFrom(typeof(TEntity)))
            {
                ents.ForEach(ent =>
                {
                    var tmp = (ent as IAuditUpdated);
                    tmp.UpdatedBy = User.Id;
                    tmp.UpdatedDate = DateTime.Now;
                });
            }
            update.SetSource(ents);
            return update;
        }

        /// <summary>
        /// 设置插入和更新时审计属性值
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="insert"></param>
        /// <param name="entity"></param>
        /// <returns></returns>
        internal static IInsert<TEntity> SetAuditValue<TEntity>(this IInsert<TEntity> insert, TEntity entity) where TEntity : class, IBaseEntity
        {
            var now = DateTime.Now;
            //插入时强制使用审计的插入值
            if (typeof(IAuditCreated).IsAssignableFrom(typeof(TEntity)))
            {
                var ent = (IAuditCreated)entity;
                ent.CreatedBy = User.Id;
                ent.CreatedDate = now;
            }
            //插入时将更新的审计属性和和插入的审计属性保持一致
            if (typeof(IAuditUpdated).IsAssignableFrom(typeof(TEntity)))
            {
                var ent = (IAuditUpdated)entity;
                ent.UpdatedBy = User.Id;
                ent.UpdatedDate = now;
            }
            //插入时强制设置为未删除
            if (typeof(IAuditDeleted).IsAssignableFrom(typeof(TEntity)))
            {
                var ent = (IAuditDeleted)entity;
                ent.IsDeleted = false;
            }
            insert.AppendData(entity);
            return insert;
        }

        /// <summary>
        /// 设置插入和更新时审计属性值
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="insert"></param>
        /// <param name="entities"></param>
        /// <returns></returns>
        internal static IInsert<TEntity> SetAuditValue<TEntity>(this IInsert<TEntity> insert, List<TEntity> entities) where TEntity : class, IBaseEntity
        {
            var now = DateTime.Now;
            //插入时强制使用审计的插入值
            if (typeof(IAuditCreated).IsAssignableFrom(typeof(TEntity)))
            {
                entities.ForEach(entity =>
                {
                    var ent = (IAuditCreated)entity;
                    ent.CreatedBy = User.Id;
                    ent.CreatedDate = now;
                });
            }
            //插入时将更新的审计属性和和插入的审计属性保持一致
            if (typeof(IAuditUpdated).IsAssignableFrom(typeof(TEntity)))
            {
                entities.ForEach(entity =>
                {
                    var ent = (IAuditUpdated)entity;
                    ent.UpdatedBy = User.Id;
                    ent.UpdatedDate = now;
                });
            }
            //插入时强制设置为未删除
            if (typeof(IAuditDeleted).IsAssignableFrom(typeof(TEntity)))
            {
                entities.ForEach(entity =>
                {
                    var ent = (IAuditDeleted)entity;
                    ent.IsDeleted = false;
                });
            }
            insert.AppendData(entities);
            return insert;
        }
        #endregion
    }
}
